package com.atguigu.gmall.item.service.impl;
import com.atguigu.gmall.cache.annotation.GmallCache;
import com.atguigu.gmall.cache.service.CacheService;
import com.atguigu.gmall.common.consts.RedisConst;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.feign.product.SkuDetailItemFeignClient;
import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.model.item.to.CategoryView;
import com.atguigu.gmall.model.item.to.SkuDetailTO;
import com.atguigu.gmall.model.product.entity.SkuInfo;
import com.atguigu.gmall.model.product.vo.SpuSaleInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author:lyd
 **/
@Slf4j
@Service
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    SkuDetailItemFeignClient skuDetailItemFeignClient;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    CacheService cacheService;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    ThreadPoolExecutor executor;

    /**
     * @param skuId
     * @return
     */
    @GmallCache(
            key =RedisConst.SKU_INFO_CACHE_PREFIX+"#{#params[0]}",
            bitmap = RedisConst.BITMAP_SKUIDS,
            bitmapOffset="#{#params[0]}",
            dataTTL = RedisConst.SKU_INFO_TTL,
            sleepTime = RedisConst.SKU_INFO_BUSINESS_SLEEP_TIME
    )
    @Override
    public SkuDetailTO getSkuDetail(Long skuId) {
        return getSkuDetailFromDatasource(skuId);
    }

    /**
     * 从分布式应用角度改写代码，将商品详情数据放入redis缓存中，提升服务器性能
     *  同时还要编写代码防止引入缓存的三大问题：缓存穿透、缓存击穿、缓存雪崩
     * @param skuId
     * @return
     */
    private SkuDetailTO getSkuDetailByRedisson(Long skuId) {
        final String cacheKey = RedisConst.SKU_INFO_CACHE_PREFIX + skuId;
        final String bitmap = RedisConst.BITMAP_SKUIDS;
        //1、第一次查询缓存
        SkuDetailTO cacheData = cacheService.getCacheData(cacheKey, SkuDetailTO.class);
        if (!StringUtils.isEmpty(cacheData)) {
            log.info("命中缓存.....");
            return cacheData;
        }
        //2、查位图，防止缓存穿透
        log.info("未命中缓存,准备回源【1、先去位图中查找】");
        Boolean isExist = redisTemplate.opsForValue().getBit(bitmap, skuId);
        if (!isExist) {
            log.warn("缓存中不存在该商品,疑似随机值攻击,注意！！！");
            throw new GmallException(ResultCodeEnum.SKU_INFO_NOT_EXIST);
        }
        //3、第二次查询缓存，防止查询位图时已建立缓存
        cacheData = cacheService.getCacheData(cacheKey, SkuDetailTO.class);
        if (!StringUtils.isEmpty(cacheData)) {
            return cacheData;
        }
        //4、加锁，防止缓存击穿
        log.info("命中位图中,准备回源【2、先去抢锁】");
        RLock lock = redissonClient.getLock(RedisConst.LOCK_PREFIX + skuId);
        if (lock.tryLock()) {
            try {
                //5、第三次查询缓存，获取锁后再次确认缓存
                cacheData = cacheService.getCacheData(cacheKey, SkuDetailTO.class);
                if (!StringUtils.isEmpty(cacheData)) {
                    return cacheData;
                }
                log.info("抢锁成功，【3、从数据库查找数据】");
                SkuDetailTO source = getSkuDetailFromDatasource(skuId);
                log.info("第一次查找该商品，放入缓存中--->商品id:[{}]", skuId);
                cacheService.save(cacheKey, source, RedisConst.SKU_INFO_TTL);
                return source;
            }finally {
                try {
                    lock.unlock();
                } catch (Exception e) {
                    log.warn("差点解到别人的锁");
                }
            }
        }
        //6、未抢到锁，短暂轮询从缓存中尝试获取
        for (int i = 0; i < 3; i++) {
            try {
                log.info("未抢到锁，短暂轮询从缓存中尝试获取");
                TimeUnit.MILLISECONDS.sleep(100);
                cacheData = cacheService.getCacheData(cacheKey, SkuDetailTO.class);
                if (!StringUtils.isEmpty(cacheData)) {
                    return cacheData;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        //7、最终方案，没有抢到锁，再次从缓存中获取数据
        return cacheService.getCacheData(cacheKey, SkuDetailTO.class);
    }

    /**
     * 远程调用从数据库中获取sku详情
     *
     * @param skuId
     * @return
     */
    private SkuDetailTO getSkuDetailFromDatasource(Long skuId) {
        //1、远程调用获取skuInfo详情
        SkuDetailTO.SkuInfoVo skuInfoVo = new SkuDetailTO.SkuInfoVo();
        SkuDetailTO skuDetailTO = new SkuDetailTO();
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = skuDetailItemFeignClient.getSkuInfo(skuId).getData();
            skuInfoVo.setId(skuInfo.getId());
            skuInfoVo.setSkuName(skuInfo.getSkuName());
            skuInfoVo.setSpuId(skuInfo.getSpuId());
            skuInfoVo.setSkuDefaultImg(skuInfo.getSkuDefaultImg());
            skuInfoVo.setWeight(skuInfo.getWeight());
            skuDetailTO.setSkuInfo(skuInfoVo);
            return skuInfo;
        }, executor);



        //2、远程调用获取categoryView详情
        CompletableFuture<Void> categoryViewFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            CategoryView categoryView = skuDetailItemFeignClient.getCategoryView(skuInfo.getCategory3Id()).getData();
            skuDetailTO.setCategoryView(categoryView);
        }, executor);


        //3、远程调用获取skuPrice详情
        CompletableFuture<Void> skuPriceFuture = CompletableFuture.runAsync(() -> {
            BigDecimal skuPrice = skuDetailItemFeignClient.getSkuPrice(skuId).getData();
            skuDetailTO.setPrice(skuPrice);
        }, executor);


        //4、远程调用获取skuImageVo
        CompletableFuture<Void> skuImageVoFuture = CompletableFuture.runAsync(() -> {
            List<SkuDetailTO.SkuImageVo> skuImageVos = skuDetailItemFeignClient.getSkuImageVoList(skuId).getData();
            skuInfoVo.setSkuImageList(skuImageVos);
            skuDetailTO.setSkuInfo(skuInfoVo);
        }, executor);


        //4、远程调用spuSaleAttrList(spu的销售属性)
        CompletableFuture<Void> spuSaleAttrsFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            List<SpuSaleInfoVo> spuSaleAttrs = skuDetailItemFeignClient.getSpuSaleAttrList(skuInfo.getSpuId(), skuId).getData();
            skuDetailTO.setSpuSaleAttrList(spuSaleAttrs);
        }, executor);


        //5、远程调用valueSkuJson
        CompletableFuture<Void> valueSkuJsonFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            String valueSkuJson = skuDetailItemFeignClient.getValueSkuJson(skuInfo.getSpuId()).getData();
            skuDetailTO.setValuesSkuJson(valueSkuJson);
        }, executor);

        // 6、等待异步线程执行完返回
        CompletableFuture.allOf(
                categoryViewFuture,
                skuPriceFuture,
                skuImageVoFuture,
                spuSaleAttrsFuture,
                valueSkuJsonFuture
        ).join();
        return skuDetailTO;
    }
}
